package com.qidi.mvcframework.servlet.aop.support;

import com.qidi.mvcframework.servlet.aop.aspect.MyAdvice;
import com.qidi.mvcframework.servlet.aop.config.MyAopConfig;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MyAdviceSupport {

    private MyAopConfig config;
    private Class<?> targetClass;
    private Object target;

    private Pattern pointCutClassPattern;

    /**
     * <需要增强的方法，<增强方法执行的时机（之前，之后，抛异常），Advice 对象>>
     * Advice 对象记录织入的方法
     */
    private Map<Method, Map<String, MyAdvice>> methodCache;

    public MyAdviceSupport(MyAopConfig config) {
        this.config = config;
    }

    /**
     * 解析配置
     */
    private void parse() {
        // 将 Spring 的表达式替换为 正则
        String pointCut = config.getPointCut()
                .replaceAll("\\.", "\\\\.")
                .replaceAll("\\\\.\\*", ".*")
                .replaceAll("\\(", "\\\\(")
                .replaceAll("\\)", "\\\\)");

        // 方法分为 3 段
        // 1 方法的修饰符
        // 2 类名
        // 3 方法名称 和 形参列表

        // 生成匹配 class 的正则
        String pointCutForClassRegex = pointCut.substring(0, pointCut.indexOf("\\(") - 4);

        // 匹配 class
        pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
        methodCache = new HashMap<>();

        // 匹配 方法
        Pattern pointCutPattern = Pattern.compile(pointCut);

        try {
            Class<?> aspectClass = Class.forName(config.getAspectClass());

            Map<String, Method> aspectMethods = new HashMap<>();

            for (Method method : aspectClass.getMethods()) {
                aspectMethods.put(method.getName(), method);
            }

            // ################# 以上都是初始化工作 #################

            // ################# 开始封装 Advice #################
            for (Method method : this.targetClass.getMethods()) {
                String methodString = method.toString();

                // method.toString() 后 会变成
                // public com.qidi.mvcframework.servlet.webmvc.servlet.MyModelAndView com.qidi.demo.controller.MyAction.add(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.lang.String,java.lang.String)
                // 和 Spring 表达式一致 都是 方法修饰符 + 类名 + 方法
                // public .* com.qidi.demo.service..*Service..*(.*)

                if (methodString.contains("throws")) {
                    // 处理抛异常的 方法
                    methodString = methodString.substring(0, methodString.indexOf("throws")).trim();
                }

                Matcher matcher = pointCutPattern.matcher(methodString);
                if (matcher.matches()) {
                    Map<String, MyAdvice> advices = new HashMap<>();
                    if (null != config.getAspectBefore() && !"".equals(config.getAspectBefore())) {
                        advices.put("before", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectBefore())));
                    }
                    if (null != config.getAspectAfter() && !"".equals(config.getAspectAfter())) {
                        advices.put("after", new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfter())));
                    }
                    if (null != config.getAspectAfterThrow() && !"".equals(config.getAspectAfterThrow())) {
                        MyAdvice advice = new MyAdvice(aspectClass.newInstance(), aspectMethods.get(config.getAspectAfterThrow()));
                        advice.setThrowName(config.getAspectAfterThrowingName());
                        advices.put("afterThrow", advice);
                    }
                    methodCache.put(method, advices);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Map<String, MyAdvice> getAdvices(Method method, Object o) throws Exception {
        Map<String, MyAdvice> cache = methodCache.get(method);
        if (cache == null) {
            Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());
            cache = methodCache.get(m);
            methodCache.put(m, cache);
        }
        return cache;
    }

    public boolean pointCutMatch() {
        return pointCutClassPattern.matcher(targetClass.toString()).matches();
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }

    public void setTargetClass(Class<?> targetClass) {
        this.targetClass = targetClass;
        parse();
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }
}
